<?php
/** Yz_SQL_DMLStatement
*
*/

abstract class Yz_SQL_DMLStatement
    extends Yz_SQL_Statement
    implements Yz_SQL_Statement_Interface
{

    const CLAUSE_WHERE = 'WHERE';
    const CLAUSE_ORDER_BY = 'ORDER BY';
    const CLAUSE_LIMIT = 'LIMIT';
    const LOGICAL_OR = 'OR';
    const LOGICAL_AND = 'AND';

    /** used for _table()
     *
     */
    const CLAUSE_TABLES = '_tables_';



    /** getSQL() : get the string representation of the current statement
    *
    *
    */
    public function getSQL()
    {
        return $this->assemble();
    } // getSQL()


    /** TODO must be a possibility to execute query as prepared stmt
    *
    *   @return <this>
    */
    public function execute()
    {
        debug();
        $this->sql = $this->assemble();

        return parent::execute();
    } // execute


    protected function _table( $clause, $table, $alias )
    {
        $this->sql = null;
        $table = Yz_String::testNotEmpty($table);
        if (null !== $alias) {
            $table .= ' ' . Yz_String::testNotEmpty($alias);
        }
        $this->clauses[$clause] = $clause;
        $this->clauses[self::CLAUSE_TABLES] = $table;

        return $this;
    }

    /**
     *
     */
    protected function _joinTable(Yz_SQL_Join $join)
    {
        debug();

        // ensure that a primary table has been specified already
        $clause =& $this->clauses[self::CLAUSE_TABLES];
        if ("" === $clause) {
            throw new Yz_SQL_Exception("Can not join unless a primary table is specified using from()");
        }
        $this->sql = null;

        $clause .= $join->getClause();
        return $this;
    }

    /**
     *
     */
    protected function _joinTables($tableList)
    {
        debug();

        // ensure that a primary table has been specified already
        $clause =& $this->clauses[self::CLAUSE_TABLES];
        if ("" === $clause) {
            throw new Yz_SQL_Exception("Can not join unless a primary table is specified using from()");
        }
        $this->sql = null;

        // verify arguments
        $tableList = new Yz_Hash($tableList);
        foreach ($tableList as $alias => $join) {
            if ($join instanceof Yz_SQL_Join) {
                $join->setAliasIfEmpty($alias);
                $clause .= $join->getClause();
            } else {
                throw new Yz_Exception("Invalid entry in the join table list");
            }
        }
        return $this;
    }


    /**
     *  Transform an array of arrays into join specification
     *      Each entry is an array of 5 elements:
     *      0: join type {LEFT JOIN|RIGHT JOIN|INNER JOIN|,|...}
     *      1: table name
     *      2: table alias
     *      3: {ON|USING}
     *      4: conditions for ON or field list for USING
     *  @param array tables
     *  @return chainable
     *  @throws Ex if the "main" table spec is empty
     */
    protected function _join(Yz_Hash $joinList)
    {
        debug();
        $clause =& $this->clauses[self::CLAUSE_TABLES];
        if ("" === $clause) {
            throw new Yz_SQL_Exception("Can not join with nothing");
        }
        $this->sql = null;
        foreach ($joinList as $alias => $table) {
            $joinType = $table->join;
            $alias = $table->alias ?: $alias;
            if ($joinType) {
                if ($table->offsetExists('joinOn')) {
                    $joinCondType = "ON";
                    $joinCond = $table->joinOn;
                } else if ($table->offsetExists('joinUsing')) {
                    $joinCondType = "USING";
                    $joinCond = $table->joinUsing;
                } else {
                    throw new Yz_SQL_Exception("No join condition (must be a string value either joinOn or joinUsing)");
                }

                $joinType = strtoupper($joinType);

                $clause .= "\n  {$joinType} JOIN {$table->name} {$alias} {$joinCondType} ({$joinCond})";
            } else {
                $clause .= ",\n  {$table->name} {$alias}";
            }
        }

        return $this;
    }

    /**
     *  empty $what removes clause
     */
    protected function _where( $clause, $what, $logicalOp='AND' )
    {
        $this->sql = null;
        if ("" !== "$what") {
            $c = $this->clauses[$clause];
            if ("" === $c) {
                $c = $clause;
            } else {
                $c .= "\n  " . $logicalOp . "\n  ";
            }
            $what = $c . "\n  " . $what;
        }
        $this->clauses[$clause] = $what;

        return $this;
    }

    /**
     *  empty $what removes clause
     */
    protected function _orderBy( $clause, $what )
    {
        $this->sql = null;
        if ("" !== "$what") {
            $c = $this->clauses[$clause];
            if ("" === $c) {
                $c = $clause;
            } else {
                $c .= ",\n  ";
            }
            $what = $c . "\n  " . $what;
        }
        $this->clauses[$clause] = $what;

        return $this;
    }


    /**
     *  @param int $offset >= 0
     *  @param int $limit  >= 0, 0 means no limit, remove limit clause entirely
     *
     */
    protected function _limit( $offset, $limit )
    {
        debug();

        // if the limit is 0, limiting is effectively disabled
        if ( $limit > 0 ) {
            $clause = $limit;

            // include offset in the clause only if it is > 0
            if ($offset > 0) {
                $clause = $offset . "," . $clause;
            }

            $clause = self::CLAUSE_LIMIT . ' ' . $clause;
        } else {
            $clause = "";
        }
        $this->sql = null;
        $this->clauses[self::CLAUSE_LIMIT] = $clause;
        return $this;
    } // limit


    /** assemble() : assemble sql statement
     *     and in some statement classes, substitute parameters
     *
     *  @return string SQL
     */
    protected function assemble()
    {
        debug();

        // cached statement
        if( null === $this->sql ) {
            if ( ! $this->isComplete() ) {
                $ex = new Yz_Exception( "The statement is not complete" );
                throw $ex;
            }
            $sql = join( "\n", array_filter(
                $this->clauses,
                function($a){return ""!=="$a";}
            ));
        } else {
            $sql = $this->sql;
        }

        return $sql;
    }


    /** testComplete() : is sql statement complete?
     *
     *  @return string SQL
     */
    abstract protected function isComplete();


} // class

